/* Copyright (c) 2008. International Fund Services (N.A.) L.L.C.
 * 1290 Avenue of the Americas 10th floor New York, NY 10104
 * All rights reserved.
 */

package org.dbwebx.jcr.impl;

import java.io.IOException;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFormatException;
import javax.jcr.version.Version;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dbwebx.adapter.jcr.util.JcrUtils;
import org.dbwebx.jcr.JcrService;
import org.dbwebx.jcr.domain.DbUnitNode;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springmodules.jcr.JcrCallback;
import org.springmodules.jcr.JcrTemplate;

/**
 * The JCR service implementation.
 * 
 * @author Izzet_Mustafayev
 * 
 */
@Repository("jcrService")
@Transactional
public class JcrServiceImpl implements JcrService, InitializingBean {
	/**
	 * The logger instance.
	 */
	private static final Log logger = LogFactory.getLog(JcrServiceImpl.class);

	/**
	 * The {@link JcrTemplate} reference.
	 */
	@Autowired
	private JcrTemplate jcrTemplate;

	/**
	 * @param jcrTemplate
	 *            the jcrTemplate to set
	 */
	public void setJcrTemplate(final JcrTemplate jcrTemplate) {
		this.jcrTemplate = jcrTemplate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.dbwebx.jcr.impl.JcrService#add(java.lang.String)
	 */
	public String add(final String nodePath) {
		String newNodePath = null;
		newNodePath = (String) jcrTemplate.execute(new JcrCallback() {
			public Object doInJcr(final Session session) throws IOException,
					RepositoryException {
				String createdNodePath = null;

				if (null != session) {
					Node root = session.getRootNode();
					Node childNode = root.addNode(JcrUtils
							.prepareRelativeNodePath(nodePath));
					session.save();
					createdNodePath = childNode.getPath();
				}
				if (logger.isDebugEnabled())
					logger.debug("DbUnit with name: " + createdNodePath
							+ " was added.");
				return createdNodePath;
			}
		});

		return newNodePath;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.dbwebx.jcr.impl.JcrService#find(java.lang.String)
	 */
	public DbUnitNode find(final String path) {
		DbUnitNode node = null;
		if (jcrTemplate.itemExists(JcrUtils.prepareRelativeNodePath(path))) {
			node = (DbUnitNode) jcrTemplate.execute(new JcrCallback() {
				public Object doInJcr(final Session session)
						throws IOException, RepositoryException {
					Node foundNode = session.getRootNode().getNode(path);
					foundNode.checkout();
					DbUnitNode node = createDBUnitNode(foundNode, true);
					return node;
				}
			});
		}
		return node;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.dbwebx.jcr.impl.JcrService#persist(java.lang.String,
	 * java.lang.String, java.lang.String)
	 */
	public DbUnitNode persist(final String basePath, final String nodeName,
			final String nodeValue) {
		DbUnitNode node = null;
		node = (DbUnitNode) jcrTemplate.execute(new JcrCallback() {
			public Object doInJcr(final Session session) throws IOException,
					RepositoryException {
				DbUnitNode newNode = null;
				if (null != session && !"".equals(basePath)) {
					Node baseNode = null;

					try {
						baseNode = session.getRootNode().getNode(
								JcrUtils.prepareRelativeNodePath(basePath));
					} catch (PathNotFoundException exc) {
						// path is not exist should create it recursively
						createNodeBasePath(basePath);
						baseNode = session.getRootNode().getNode(
								JcrUtils.prepareRelativeNodePath(basePath));
					}
					String newNodePath = baseNode.getPath() + "/" + nodeName;
					boolean isNewNode = session.itemExists(newNodePath);
					if (null != baseNode && isNewNode) {
						Node childNode = session.getRootNode().getNode(
								JcrUtils.prepareRelativeNodePath(newNodePath));
						childNode.checkout();
						childNode.setProperty(DbUnitNode.DBU_SQL, nodeValue);
						session.save();
						newNode = createDBUnitNode(childNode, true);
					}
					if (null != baseNode && !isNewNode) {
						Node childNode = baseNode.addNode(nodeName);
						childNode.addMixin("mix:versionable");
						childNode.setProperty(DbUnitNode.DBU_SQL, nodeValue);
						session.save();
						newNode = createDBUnitNode(childNode, true);
					}
					if (logger.isDebugEnabled())
						logger.debug("DbUnit with name: " + nodeName
								+ " was added.");
				}
				return newNode;
			}
		});

		return node;
	}

	/**
	 * Recursively created required path.
	 * 
	 * @param basePath
	 *            the path to create
	 */
	private void createNodeBasePath(final String basePath) {
		String[] pathChunks = basePath.split("/");
		String path = "";
		for (int i = 0; i < pathChunks.length; i++) {
			if ("".equals(pathChunks[i])) {
				continue;
			}
			path += "/" + pathChunks[i];
			if (!jcrTemplate.itemExists(path)) {
				add(path);
			}
		}
	}

	/**
	 * Maps JCR node to {@link DbUnitNode}.
	 * 
	 * @param jcrNode
	 *            the JCR node to create {@link DbUnitNode} from
	 * @param isNewVersionRequired
	 *            flag to create or not new node version
	 * @return created {@link DbUnitNode}
	 */
	private DbUnitNode createDBUnitNode(final Node jcrNode,
			boolean isNewVersionRequired) {
		DbUnitNode node = null;
		try {
			node = new DbUnitNode();
			if (isNewVersionRequired) {// create new node version
				Version version = jcrNode.checkin();
				node.setVersion(version.getName());
			}
			node.setData(jcrNode.getProperty(DbUnitNode.DBU_SQL).getString());
			node.setPath(jcrNode.getPath());
			node.setName(jcrNode.getName());
		} catch (ValueFormatException exc) {
			logger.error("Error occured on creating DbUnitNode.", exc);
		} catch (PathNotFoundException exc) {
			logger.error("Error occured on creating DbUnitNode.", exc);
		} catch (RepositoryException exc) {
			logger.error("Error occured on creating DbUnitNode.", exc);
		}

		return node;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(jcrTemplate, "jcrTemplate should not be null.");
	}
}
